/*
StickyInput v1.1 - An abstraction layer for keyboard and joystick input.
by Jonathan Dearborn 1/25/10

StickyInput presents a uniform interface for keyboard and joystick input.
It supports keyboard input as well as joystick axes, buttons, and hats.  Each
button is distinct, so that input can easily be a combination of keyboard
and any number of joysticks for any user.  This library is designed such that
you never need to touch an SDL_Joystick pointer or even the SDL Joystick API
at all.

Requires:
    SDL ("SDL.h") [www.libsdl.org]

The easiest way to use StickyInput is to create an instance of the Controller class
for each player.  Add buttons to each controller using addButton().  Then you test those 
buttons in the game loop.

See the Multiplayer demo for an example implementation.

Declarations:

class Button;
	// The main class that wraps keyboard and joystick input.
	Button(SDLKey key);
		// Sets the object to represent a keyboard key.
	Button(TypeEnum type, unsigned int joystick, int buttonIndex, int deadZone = 100);
		// Sets the object to represent a joystick item (axis/button/hat).
	bool checkHold();
		// Returns true if the item is being held in its active position.
	bool checkDown(const SDL_Event& event);
		// Returns true if the event describes this item being pressed.
	bool checkUp(const SDL_Event& event);
		// Returns true if the event describes this item being released.
	Uint16 getState();
		// Returns the current value of the button.  This value ranges from 0
		// to 32767.
    
class JoystickInfo;
	// A class that holds all of the info about a joystick.
	unsigned int index;
	SDL_Joystick* joystick;
	const char* name;
	unsigned int axes, buttons, hats, balls;
	JoystickInfo();
		// Zero-initializes the object.
	JoystickInfo(unsigned int index);
		// Loads the indicated joystick's info.
	bool load(unsigned int index);
		// Loads the indicated joystick's info.

void Reset(bool print);
	// Initializes (or resets) the SDL joystick subsystem and initializes StickyInput.
	// You can use this to identify new joysticks at any time.  If 'print' is
	// true, it will print info to std output.
inline void Reset();
	// Same as above, with print == true.  This is provided for simple function pointer compatibility.
inline bool WasInit();
	// Returns true if Reset() has been called successfully.
inline int NumJoysticks();
	// Returns the number of recognized joysticks.
Button WaitPress(Button* button = NULL, int deadZone = 100, Uint32 delay = 100);
	// Enters a loop until a keyboard key or joystick item is pressed.
	// Returns an object to describe the press.  You can then use that object
	// as an input for a player, for example.  It is suggested to cancel that
	// action if the object represents SDLK_ESCAPE.

void PrintButton(const Button& button);
	// Prints information about a button to std output.
void MonitorInput(void (*fn)(Button) = PrintButton, int deadZone = 100);
	// Enters a loop that calls the given function whenever an input event that could
	// be wrapped by Button occurs.  The default behavior is to print the event
	// to std output.


License:
    The short:
    Use it however you'd like, but give me credit if you share this code
    or a compiled version of the code, whether or not it is modified.
    
    The long:
    Copyright (c) 2010, Jonathan Dearborn
    All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this list 
      of conditions and the following disclaimer.
    * Redistributions in binary form, excluding commercial executables, must reproduce 
      the above copyright notice, this list of conditions and the following disclaimer 
      in the documentation and/or other materials provided with the distribution.
    * The names of its contributors may not be used to endorse or promote products 
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/




#ifndef _STICKYINPUT_H__
#define _STICKYINPUT_H__

#include "SDL.h"
#include <vector>
#include <climits>
#include <stdexcept>

namespace SI
{

class Button;  // See below for definition

/*
Reset() closes any open joysticks and (re)initializes SDL's joystick subsystem.
This causes any newly plugged joysticks to be detected.  It also initializes the
keystates for SI.
*/
void Reset(bool print);
inline void Reset()  // Done this way to allow no-arg function pointers to use it.
{
    Reset(false);
}

extern bool wasinit;
/*
WasInit() returns true if Reset() was called successfully.
*/
inline bool WasInit()
{
    return wasinit;
}

extern unsigned int numjoysticks;

/*
NumJoysticks() returns the number of detected joysticks.  This is updated
by Reset().
*/
inline int NumJoysticks()
{
    return numjoysticks;
}

/*
WaitPress() waits in a loop until a valid input 'button' is pressed.
These 'buttons' can be a keyboard key, mouse button, or any axis/button/hat
on a joystick.  If the optional pointer to a Button object is filled in, then
that will be set to the same value as the return value.
*/
Button WaitPress(Button* button = NULL, int deadZone = 100, Uint32 waitTime = 0, Uint32 delay = 100);



void PrintButton(const Button& button);

/*
MonitorInput() runs a loop, waiting for input events.  When an event
happens, the information is sent to the function that is given as an argument.
That functions then should present the information to the user somehow.
A sample function is provided, PrintButton().
When SDLK_ESCAPE is pressed, the loop is ended and the function returns.
*/
void MonitorInput(void (*fn)(const Button&) = PrintButton, int deadZone = 100);



// This also affects keyboard a little...
inline void GrabInput(bool enable)
{
    SDL_WM_GrabInput(enable? SDL_GRAB_ON : SDL_GRAB_OFF);
}
inline bool IsInputGrabbed()
{
    return (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON);
}




/*
JoystickInfo is a class that describes the details of a joystick.
It contains all the information that can be gotten from the SDL joystick 
functions (.index, .joystick, .name, .axes, .buttons, .hats, .balls).
*/
class JoystickInfo
{
    public:
    unsigned int index;
    SDL_Joystick* joystick;
    const char* name;
    unsigned int axes, buttons, hats, balls;
    
    JoystickInfo()
        : index(0)
        , joystick(NULL)
        , name(NULL)
        , axes(0), buttons(0), hats(0), balls(0)
    {}
    JoystickInfo(unsigned int index)
        : index(0)
        , joystick(NULL)
        , name(NULL)
        , axes(0), buttons(0), hats(0), balls(0)
    {
        load(index);
    }
    
    bool load(unsigned int index);
};





/*
Button is an abstraction class for keyboard and joystick input.
It wraps the two into one interface that makes keyboard, joystick, and mouse
button input handling identical.  It handles key presses, mouse buttons, 
joystick buttons, joystick axes, and joystick hats.
*/
class Button
{
    public:

    enum TypeEnum{KEY, MOUSEBUTTON, JOYBUTTON, JOYAXIS_NEG, JOYAXIS_POS,
                     JOYHAT_CENTER, JOYHAT_UP, JOYHAT_DOWN, 
                     JOYHAT_LEFT, JOYHAT_RIGHT, JOYHAT_LEFTUP, 
                     JOYHAT_LEFTDOWN, JOYHAT_RIGHTUP, JOYHAT_RIGHTDOWN};
    
    TypeEnum type;
    int joystick;
    int joyItem;  // An integer identifying the item (axis/button/hat) on the joystick
    int deadZone;  // Threshold region (-deadZone < x < deadZone) for a joystick axis
    int oldAxis;  // Used to determine release events for axes
    Uint8 oldHat;  // Used to determine release events for hats
    bool distinctHatDirections; // Makes things like RIGHT act as a completely different button than RIGHTUP.
                                  // Otherwise, the hat is treated like a joystick or d-pad where the directions mix.
    SDLKey key;  // The keyboard key that this object represents
    unsigned int mouse;  // The mouse that holds our button
    unsigned int mouseButton;  // The mouse button index
    
    static std::string getTypeName(Button::TypeEnum type)
    {
        switch(type)
        {
            case KEY:
            return "KEY";
            case MOUSEBUTTON:
            return "MOUSEBUTTON";
            case JOYBUTTON:
            return "JOYBUTTON";
            case JOYAXIS_NEG:
            return "JOYAXIS_NEG";
            case JOYAXIS_POS:
            return "JOYAXIS_POS";
            case JOYHAT_CENTER:
            return "JOYHAT_CENTER";
            case JOYHAT_UP:
            return "JOYHAT_UP";
            case JOYHAT_DOWN:
            return "JOYHAT_DOWN";
            case JOYHAT_LEFT:
            return "JOYHAT_LEFT";
            case JOYHAT_RIGHT:
            return "JOYHAT_RIGHT";
            case JOYHAT_LEFTUP:
            return "JOYHAT_LEFTUP";
            case JOYHAT_LEFTDOWN:
            return "JOYHAT_LEFTDOWN";
            case JOYHAT_RIGHTUP:
            return "JOYHAT_RIGHTUP";
            case JOYHAT_RIGHTDOWN:
            return "JOYHAT_RIGHTDOWN";
        }
        return "";
    }
    
    static Button::TypeEnum getTypeFromName(const std::string& type)
    {
        if(type == "KEY")
            return KEY;
        if(type == "MOUSEBUTTON")
            return MOUSEBUTTON;
        if(type == "JOYBUTTON")
            return JOYBUTTON;
        if(type == "JOYAXIS_NEG")
            return JOYAXIS_NEG;
        if(type == "JOYAXIS_POS")
            return JOYAXIS_POS;
        if(type == "JOYHAT_CENTER")
            return JOYHAT_CENTER;
        if(type == "JOYHAT_UP")
            return JOYHAT_UP;
        if(type == "JOYHAT_DOWN")
            return JOYHAT_DOWN;
        if(type == "JOYHAT_LEFT")
            return JOYHAT_LEFT;
        if(type == "JOYHAT_RIGHT")
            return JOYHAT_RIGHT;
        if(type == "JOYHAT_LEFTUP")
            return JOYHAT_LEFTUP;
        if(type == "JOYHAT_LEFTDOWN")
            return JOYHAT_LEFTDOWN;
        if(type == "JOYHAT_RIGHTUP")
            return JOYHAT_RIGHTUP;
        if(type == "JOYHAT_RIGHTDOWN")
            return JOYHAT_RIGHTDOWN;
        
        return KEY;
    }
    
    Button();
    Button(SDLKey key);
    Button(TypeEnum type, int joystick, int buttonIndex, int deadZone = 100);
    // When I implement multiple mice, this constructor needs to be differentiated from the joystick one.
    // Or I should unite the constructors and test the type.
    Button(TypeEnum type, /*unsigned int mouseIndex,*/ unsigned int buttonIndex);
    /*Button(const Button& button);
    Button& operator=(const Button& button);*/
    
    bool checkHold();
    
    bool checkDown(const SDL_Event& event);
    
    bool checkUp(const SDL_Event& event);
    
    Uint16 getState();
    
};


/*
Cursor is an abstraction of pointing devices that wraps mouse, keyboard, 
joystick, and trackball input.  It contains x-y coordinates to represent the
location of the cursor.
*/
class Cursor
{
    public:
    enum TypeEnum{MOUSE, BUTTONS, TRACKBALL};
    int x, y;
    int dx, dy;
    
    float vel;
    float minVel;
    float maxVel;
    float accel;
    
    float my_dt;
    
    TypeEnum type;
    unsigned int mouse;
    Button* button;  // Always an array of 4 buttons
    unsigned int joystick;  // The joystick of the trackball
    unsigned int trackball;
    
    bool updateMouseByState;
    bool updateTrackballByState;  // Enables update() to fill in the position data
                              // You might want it disabled if you have both
                              // update() functions working in the same loop.
    bool useRelativeMouse;  // Uses only relative changes so that you can control the acceleration
    //bool relative;  // Use only relative changes so you can always control acceleration and velocity
    bool relativeOneForOne;  // Uses relative data directly, without controlling velocity
    bool stopOnOppositePress;  // Causes the cursor to stop if two opposite directions are pressed
    
    Cursor(unsigned int mouseIndex = 0)
        : x(0), y(0)
        , dx(0), dy(0)
        , vel(100), minVel(100), maxVel(1000)
        , accel(1000)
        , my_dt(0)
        , type(MOUSE)
        , mouse(mouseIndex)
        , button(NULL)
        , joystick(0)
        , trackball(0)
        , updateMouseByState(false)
        , updateTrackballByState(false)
        , relativeOneForOne(true)
        , stopOnOppositePress(true)
    {}
    Cursor(Button up, Button down, Button left, Button right)
        : x(0), y(0)
        , dx(0), dy(0)
        , vel(100), minVel(100), maxVel(1000)
        , accel(1000)
        , my_dt(0)
        , type(BUTTONS)
        , mouse(0)
        , button(NULL)
        , joystick(0)
        , trackball(0)
        , updateMouseByState(false)
        , updateTrackballByState(false)
        , relativeOneForOne(true)
        , stopOnOppositePress(true)
    {
        button = new Button[4];
        button[0] = up;
        button[1] = down;
        button[2] = left;
        button[3] = right;
    }
    Cursor(unsigned int joystick, unsigned int ballIndex)
        : x(0), y(0)
        , dx(0), dy(0)
        , vel(100), minVel(100), maxVel(1000)
        , accel(1000)
        , my_dt(0)
        , type(TRACKBALL)
        , mouse(0)
        , button(NULL)
        , joystick(joystick)
        , trackball(ballIndex)
        , updateMouseByState(false)
        , updateTrackballByState(false)
        , relativeOneForOne(true)
        , stopOnOppositePress(true)
    {}
    
    ~Cursor()
    {
        delete[] button;
    }

    bool load(unsigned int mouseIndex)
    {
        type = MOUSE;
        mouse = mouseIndex;
        return true;
    }
    bool load(Button up, Button down, Button left, Button right)
    {
        type = BUTTONS;
        if(button == NULL)
            button = new Button[4];
        button[0] = up;
        button[1] = down;
        button[2] = left;
        button[3] = right;
        return true;
    }
    bool load(unsigned int joystick, unsigned int ballIndex)
    {
        type = TRACKBALL;
        this->joystick = joystick;
        trackball = ballIndex;
        return true;
    }

    // Checks the Buttons or sets the mouse position each frame
    // Also piles up deltas if events are not being used.
    void update(float dt);
    void update(const SDL_Event& event);  // Uses the mouse or ball.  I could try adding built-in states for buttons.
    
};


class DirectionSet
{
    public:
    
    
    class State
    {
        public:
        float dirX;
        float dirY;
        float magnitude;
        
        State();
    };
    
    Button up;
    Button down;
    Button left;
    Button right;
    
    State state;
    
    DirectionSet();
    DirectionSet(const Button& up, const Button& down, const Button& left, const Button& right);
    
    void update(bool circular = false);
};


class Controller;

void loadDefaultController(Controller& controller);
void loadDefaultController(Controller& controller, unsigned int joystick);

class Controller
{
    public:
    static const unsigned int NO_BUTTON = UINT_MAX;
    std::vector<Button> buttons;
    
    Controller();
    Controller(unsigned int joystick);
    
    unsigned int addButton(const Button& button);
    
    void setButton(unsigned int buttonIndex, const Button& button);
    
    Button& getButton(unsigned int buttonIndex);
    
    void deleteButtons();
    
    bool checkHold(unsigned int buttonIndex);


    unsigned int checkInput(SDL_Event& event, bool checkDown = true);
};




} // END namespace SI

#endif
